﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Pkcs;
using System.Runtime.InteropServices;
using System.ComponentModel;
using Microsoft.Win32.SafeHandles;
using System.Security.Cryptography.Xml;

namespace LargeCMS
{
    partial class Win32
    {
        #region "HELPER FUNCTIONS"

        public static SafeCSPHandle GetCSPHandle(X509Certificate2 cert)
        {
            // Variables
            SafeCSPHandle hProv = SafeCSPHandle.Null;
            RSACryptoServiceProvider key = null;
            bool bResult = false;

            // Get CSP handle            
            key = (RSACryptoServiceProvider)cert.PrivateKey;
            bResult = CryptAcquireContext(
                out hProv,
                key.CspKeyContainerInfo.KeyContainerName,
                key.CspKeyContainerInfo.ProviderName,
                key.CspKeyContainerInfo.ProviderType,
                0
            );
            if (!bResult)
            {
                throw new Exception("CryptAcquireContext error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
            }

            // Return handle
            return hProv;
        }

        public static void EncodeSigningTime(DateTime signingTime, out SafeNTHeapHandle pbEncodedSigningTime, out int cbEncodedSigningTime)
        {
            // Variables
            long lFileTime = 0;
            bool bResult = false;

            // Get time in filetime format
            lFileTime = signingTime.ToFileTime();

            // Get size for encoded data
            pbEncodedSigningTime = SafeNTHeapHandle.Null;
            cbEncodedSigningTime = 0;
            bResult = CryptEncodeObject(
                X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
                szOID_RSA_signingTime,
                ref lFileTime,
                pbEncodedSigningTime,
                ref cbEncodedSigningTime
            );
            if (!bResult)
            {
                throw new Exception("CryptEncodeObject error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
            }

            // Create buffer for encoded data
            pbEncodedSigningTime = new SafeNTHeapHandle(Marshal.AllocHGlobal(cbEncodedSigningTime));

            // Get encoded data
            bResult = CryptEncodeObject(
                X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
                szOID_RSA_signingTime,
                ref lFileTime,
                pbEncodedSigningTime,
                ref cbEncodedSigningTime
            );
            if (!bResult)
            {
                throw new Exception("CryptEncodeObject error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
            }
        }

        public static CRYPT_ATTRIBUTE CreateCryptAttribute(string pszObjId, SafeNTHeapHandle pbEncodedData, int cbEncodedData)
        {
            // Variables
            BLOB CryptAttrBlob = new BLOB();
            CRYPT_ATTRIBUTE CryptAttr = new CRYPT_ATTRIBUTE();

            // Populate Crypt Attribute struct
            CryptAttrBlob.cbData = cbEncodedData;
            CryptAttrBlob.pbData = pbEncodedData.DangerousGetHandle();

            CryptAttr.pszObjId = pszObjId;
            CryptAttr.cValue = 1;
            CryptAttr.rgValue = Marshal.AllocHGlobal(Marshal.SizeOf(CryptAttrBlob));
            Marshal.StructureToPtr(CryptAttrBlob, CryptAttr.rgValue, false);

            // Return the struct
            return CryptAttr;
        }

        public static CMSG_SIGNER_ENCODE_INFO CreateSignerEncodeInfo(X509Certificate2 cert, CRYPT_ATTRIBUTE CryptAttr)
        {
            // Variables
            CERT_CONTEXT CertContext = new CERT_CONTEXT();
            CMSG_SIGNER_ENCODE_INFO SignerEncodeInfo = new CMSG_SIGNER_ENCODE_INFO();
            SafeCSPHandle hProv = SafeCSPHandle.Null;

            // Get CSP of certificate
            hProv = GetCSPHandle(cert);

            // Get context of certificate
            CertContext = (CERT_CONTEXT)Marshal.PtrToStructure(cert.Handle, typeof(CERT_CONTEXT));

            // Populate Signer Info struct
            SignerEncodeInfo.cbSize = Marshal.SizeOf(SignerEncodeInfo);
            SignerEncodeInfo.pCertInfo = CertContext.pCertInfo;
            SignerEncodeInfo.hCryptProvOrhNCryptKey = hProv.DangerousGetHandle();
            GC.SuppressFinalize(hProv);
            SignerEncodeInfo.dwKeySpec = (int)((RSACryptoServiceProvider)cert.PrivateKey).CspKeyContainerInfo.KeyNumber;
            SignerEncodeInfo.HashAlgorithm.pszObjId = szOID_OIWSEC_sha1;
            // Add auth attribute to it
            SignerEncodeInfo.cAuthAttr = 1;
            SignerEncodeInfo.rgAuthAttr = Marshal.AllocHGlobal(Marshal.SizeOf(CryptAttr));
            Marshal.StructureToPtr(CryptAttr, SignerEncodeInfo.rgAuthAttr, false);

            // Return the struct
            return SignerEncodeInfo;
        }

        public static CMSG_SIGNED_ENCODE_INFO CreateSignedEncodeInfo(X509Certificate2Collection certs, CMSG_SIGNER_ENCODE_INFO SignerInfo)
        {
            // Variables 
            CMSG_SIGNED_ENCODE_INFO SignedEncodeInfo = new CMSG_SIGNED_ENCODE_INFO();
            CERT_CONTEXT[] CertContexts = null;
            BLOB[] CertBlobs = null;
            int iCount = 0;

            // Get context of all certs
            CertContexts = new CERT_CONTEXT[certs.Count];
            foreach (X509Certificate2 cert in certs)
            {
                CertContexts[iCount] = (CERT_CONTEXT)Marshal.PtrToStructure(cert.Handle, typeof(CERT_CONTEXT));
                iCount++;
            }

            // Get cert blob of all certs
            CertBlobs = new BLOB[CertContexts.Length];
            for (iCount = 0; iCount < CertContexts.Length; iCount++)
            {
                CertBlobs[iCount].cbData = CertContexts[iCount].cbCertEncoded;
                CertBlobs[iCount].pbData = CertContexts[iCount].pbCertEncoded;
            }

            // Populate Signed Encode Info struct
            SignedEncodeInfo.cbSize = Marshal.SizeOf(SignedEncodeInfo);

            SignedEncodeInfo.cSigners = 1;
            SignedEncodeInfo.rgSigners = Marshal.AllocHGlobal(Marshal.SizeOf(SignerInfo));
            Marshal.StructureToPtr(SignerInfo, SignedEncodeInfo.rgSigners, false);

            SignedEncodeInfo.cCertEncoded = CertBlobs.Length;
            SignedEncodeInfo.rgCertEncoded = Marshal.AllocHGlobal(Marshal.SizeOf(CertBlobs[0]) * CertBlobs.Length);
            for (iCount = 0; iCount < CertBlobs.Length; iCount++)
            {
                Marshal.StructureToPtr(CertBlobs[iCount], new IntPtr(SignedEncodeInfo.rgCertEncoded.ToInt64() + (Marshal.SizeOf(CertBlobs[iCount]) * iCount)), false);
            }

            return SignedEncodeInfo;
        }

        public static CMSG_ENVELOPED_ENCODE_INFO CreateEnvelopedEncodeInfo(X509Certificate2Collection recipients, string algorithm)
        {
            // Variables
            CMSG_ENVELOPED_ENCODE_INFO EnvelopedEncodeInfo = new CMSG_ENVELOPED_ENCODE_INFO();
            CMSG_RC4_AUX_INFO AuxInfo = new CMSG_RC4_AUX_INFO();
            CERT_CONTEXT[] RecipientContexts = null;
            CERT_CONTEXT[] CertContexts = null;
            BLOB[] CertBlobs = null;
            X509Chain chain = null;
            X509Certificate2Collection certs = null;
            int iCount = 0;

            // Get context of all recipients, and all certs in their cert chains (without duplicates)
            RecipientContexts = new CERT_CONTEXT[recipients.Count];
            certs = new X509Certificate2Collection();
            iCount = 0;
            foreach (X509Certificate2 recipient in recipients)
            {
                // Context of recipient cert
                RecipientContexts[iCount] = (CERT_CONTEXT)Marshal.PtrToStructure(recipient.Handle, typeof(CERT_CONTEXT));

                // Cert chain of recipient cert
                chain = new X509Chain();
                chain.Build(recipient);
                foreach (X509ChainElement chainElement in chain.ChainElements)
                {
                    if (!certs.Contains(chainElement.Certificate))
                    {
                        certs.Add(chainElement.Certificate);
                    }
                }

                iCount++;
            }

            // Get context of all certs
            CertContexts = new CERT_CONTEXT[certs.Count];
            iCount = 0;
            foreach (X509Certificate2 cert in certs)
            {
                CertContexts[iCount] = (CERT_CONTEXT)Marshal.PtrToStructure(cert.Handle, typeof(CERT_CONTEXT));
                iCount++;
            }

            // Get cert blob of all certs
            CertBlobs = new BLOB[CertContexts.Length];
            for (iCount = 0; iCount < CertContexts.Length; iCount++)
            {
                CertBlobs[iCount].cbData = CertContexts[iCount].cbCertEncoded;
                CertBlobs[iCount].pbData = CertContexts[iCount].pbCertEncoded;
            }

            // Populate Enveloped Encode Info struct
            EnvelopedEncodeInfo.cbSize = Marshal.SizeOf(EnvelopedEncodeInfo);

            // Algorithm
            EnvelopedEncodeInfo.ContentEncryptionAlgorithm.pszObjId = algorithm;
            switch (algorithm)
            {
                // RC4
                case szOID_RSA_RC4:
                    // RC4 Aux Info
                    AuxInfo.cbSize = Marshal.SizeOf(AuxInfo);
                    AuxInfo.dwBitLen = CMSG_RC4_NO_SALT_FLAG;
                    EnvelopedEncodeInfo.pvEncryptionAuxInfo = Marshal.AllocHGlobal(Marshal.SizeOf(AuxInfo));
                    Marshal.StructureToPtr(AuxInfo, EnvelopedEncodeInfo.pvEncryptionAuxInfo, false);
                    break;

                // 3DES
                case szOID_RSA_DES_EDE3_CBC:
                    // IV generated automatically
                    EnvelopedEncodeInfo.ContentEncryptionAlgorithm.Parameters.cbData = 0;
                    EnvelopedEncodeInfo.pvEncryptionAuxInfo = IntPtr.Zero;
                    break;
                default:
                    break;
            }

            // Recipients
            EnvelopedEncodeInfo.cRecipients = RecipientContexts.Length;
            EnvelopedEncodeInfo.rgpRecipients = Marshal.AllocHGlobal(Marshal.SizeOf(RecipientContexts[0].pCertInfo) * RecipientContexts.Length);
            for (iCount = 0; iCount < RecipientContexts.Length; iCount++)
            {
                Marshal.WriteIntPtr(new IntPtr(EnvelopedEncodeInfo.rgpRecipients.ToInt64() + Marshal.SizeOf(RecipientContexts[iCount].pCertInfo) * iCount), RecipientContexts[iCount].pCertInfo);
            }

            // Certs
            EnvelopedEncodeInfo.cCertEncoded = CertBlobs.Length;
            EnvelopedEncodeInfo.rgCertEncoded = Marshal.AllocHGlobal(Marshal.SizeOf(CertBlobs[0]) * CertBlobs.Length);
            for (iCount = 0; iCount < CertBlobs.Length; iCount++)
            {
                Marshal.StructureToPtr(CertBlobs[iCount], new IntPtr(EnvelopedEncodeInfo.rgCertEncoded.ToInt64() + (Marshal.SizeOf(CertBlobs[iCount]) * iCount)), false);
            }

            return EnvelopedEncodeInfo;
        }

        public static CMSG_STREAM_INFO CreateStreamInfo(int dwFileSize, StreamOutputCallbackDelegate pfnCallback)
        {
            // Variables
            CMSG_STREAM_INFO StreamInfo = new CMSG_STREAM_INFO();

            // Populate Stream Info struct
            StreamInfo.cbContent = dwFileSize;
            StreamInfo.pfnStreamOutput = pfnCallback;

            // Return it
            return StreamInfo;
        }

        public static SafeMsgHandle OpenMessageToEncode(CMSG_SIGNED_ENCODE_INFO SignedEncodeInfo, CMSG_STREAM_INFO StreamInfo, bool bDetached)
        {
            // Variables
            SafeMsgHandle hMsg = SafeMsgHandle.Null;

            // Open message to encode
            hMsg = CryptMsgOpenToEncode(
                X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
                CMSG_AUTHENTICATED_ATTRIBUTES_FLAG | (bDetached ? CMSG_DETACHED_FLAG : 0),
                CMSG_SIGNED,
                ref SignedEncodeInfo,
                null,
                ref StreamInfo
            );
            if ((hMsg == null) || (hMsg.IsInvalid))
            {
                throw new Exception("CryptMsgOpenToEncode error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
            }

            return hMsg;
        }

        public static SafeMsgHandle OpenMessageToEncode(CMSG_ENVELOPED_ENCODE_INFO EnvelopedEncodeInfo, CMSG_STREAM_INFO StreamInfo)
        {
            // Variables
            SafeMsgHandle hMsg = SafeMsgHandle.Null;

            // Open message to encode
            hMsg = CryptMsgOpenToEncode(
                X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
                0,
                CMSG_ENVELOPED,
                ref EnvelopedEncodeInfo,
                null,
                ref StreamInfo
            );
            if ((hMsg == null) || (hMsg.IsInvalid))
            {
                throw new Exception("CryptMsgOpenToEncode error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
            }

            return hMsg;
        }

        public static SafeMsgHandle OpenMessageToDecode(CMSG_STREAM_INFO StreamInfo)
        {
            // Open message to decode
            return OpenMessageToDecode(StreamInfo, false);
        }

        public static SafeMsgHandle OpenMessageToDecode(CMSG_STREAM_INFO StreamInfo, bool bDetached)
        {
            // Variables
            SafeMsgHandle hMsg = SafeMsgHandle.Null;

            // Open message to decode
            hMsg = CryptMsgOpenToDecode(
                X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
                bDetached? CMSG_DETACHED_FLAG: 0,
                0,
                IntPtr.Zero,
                IntPtr.Zero,
                ref StreamInfo
            );
            if ((hMsg == null) || (hMsg.IsInvalid))
            {
                throw new Exception("CryptMsgOpenToDecode error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
            }

            return hMsg;
        }

        public unsafe static void ProcessMessage(SafeMsgHandle hMsg, FileStream file)
        {
            // Variables
            BinaryReader stream = null;
            byte[] pbData = null;
            long dwRemaining = 0;
            bool bFinal = false;
            bool bResult = false;

            // Process message
            dwRemaining = file.Length;
            stream = new BinaryReader(file);
            do
            {
                // Read one chunk of data
                pbData = stream.ReadBytes(1024 * 1000 * 100);
                if (pbData.Length == 0)
                {
                    break;
                }

                // Update message piece by piece    
                bFinal = (dwRemaining <= 1024 * 1000 * 100);
                fixed (byte* pAux = &pbData[0])
                {
                    bResult = CryptMsgUpdate(
                        hMsg.DangerousGetHandle(),
                        new IntPtr(pAux),
                        pbData.Length,
                        bFinal
                    );
                    if (!bResult)
                    {
                        throw new Exception("CryptMsgUpdate error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
                    }
                }
                dwRemaining = dwRemaining - pbData.Length;

            } while (!bFinal);
        }

        public static void GetMessageParam(SafeMsgHandle hMsg, int dwParamType, out int iParam)
        {
            // Variables 
            int cbParam = 0;
            bool bResult = false;

            // Get integer param
            iParam = 0;
            cbParam = Marshal.SizeOf(iParam);
            bResult = CryptMsgGetParam(
                hMsg,
                dwParamType,
                0,
                out iParam,
                ref cbParam
            );
            if (!bResult)
            {
                throw new Exception("CryptMsgGetParam error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
            }
        }

        public static void GetMessageParam(SafeMsgHandle hMsg, int dwParamType, out SafeNTHeapHandle pParam)
        {
            // Variables 
            int cbParam = 0;

            // Get param
            GetMessageParam(hMsg, dwParamType, 0, out pParam, out cbParam);
        }

        public static void GetMessageParam(SafeMsgHandle hMsg, int dwParamType, int dwIndex, out SafeNTHeapHandle pParam)
        {
            // Variables 
            int cbParam = 0;

            // Get param
            GetMessageParam(hMsg, dwParamType, dwIndex, out pParam, out cbParam);
        }

        public static void GetMessageParam(SafeMsgHandle hMsg, int dwParamType, int dwIndex, out SafeNTHeapHandle pParam, out int cbParam)
        {
            // Variables
            bool bResult = false;

            // Get size of data
            pParam = SafeNTHeapHandle.Null;
            cbParam = 0;
            bResult = CryptMsgGetParam(
                hMsg,
                dwParamType,
                dwIndex,
                pParam,
                ref cbParam
            );
            if (!bResult)
            {
                throw new Exception("CryptMsgGetParam error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
            }

            // Create buffer for data
            pParam = new SafeNTHeapHandle(Marshal.AllocHGlobal(cbParam));

            // Memory is not zero-filled. Do it, otherwise we will AV when marshalling some fields of structs
            MemSet(
                pParam.DangerousGetHandle(), 
                0, 
                cbParam
            );

            // Get data
            bResult = CryptMsgGetParam(
                hMsg,
                dwParamType,
                dwIndex,
                pParam,
                ref cbParam
            );
            if (!bResult)
            {
                throw new Exception("CryptMsgGetParam error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
            }
        }

        public static void GetCertContextProperty(SafeCertContextHandle pCertContext,  int dwPropId, out SafeNTHeapHandle pvData)
        {
            // Variables
            int cbData = 0;
            bool bResult = false;

            // Get size of data
            pvData = SafeNTHeapHandle.Null;
            bResult = CertGetCertificateContextProperty(
                pCertContext,
                dwPropId,
                pvData,
                ref cbData
            );
            if (!bResult)
            {
                throw new Exception("CertGetCertificateContextProperty error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
            }

            // Create buffer for data
            pvData = new SafeNTHeapHandle(Marshal.AllocHGlobal(cbData));

            // Get data
            bResult = CertGetCertificateContextProperty(
                pCertContext,
                dwPropId,
                pvData,
                ref cbData
            );
            if (!bResult)
            {
                throw new Exception("CertGetCertificateContextProperty error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
            }
        }

        public static DateTime[] GetSigningTimes(SafeMsgHandle hMsg)
        { 
            // Variables
            CMSG_SIGNER_INFO SignerInfo = new CMSG_SIGNER_INFO();
            CRYPT_ATTRIBUTE CryptAttr = new CRYPT_ATTRIBUTE();
            BLOB CryptAttrBlob = new BLOB();
            DateTime[] signingTimes = null;
            SafeNTHeapHandle pSignerInfo = SafeNTHeapHandle.Null;
            IntPtr pCryptAttr = IntPtr.Zero;
            long lFileTime = 0;
            int cbFileTime = 0;
            int iCount = 0;
            bool bResult = false;

            try
            {
                // Get number of signers
                iCount = GetSignerCount(hMsg);

                // Create array of signing times 
                signingTimes = new DateTime[iCount];

                // Get all signing times
                for (int i = 0; i < iCount; i++)
                {
                    // Get signer info
                    GetMessageParam(hMsg, CMSG_SIGNER_INFO_PARAM, i, out pSignerInfo);

                    // Get signing time from authenticated attributes
                    SignerInfo = (CMSG_SIGNER_INFO)Marshal.PtrToStructure(pSignerInfo.DangerousGetHandle(), typeof(CMSG_SIGNER_INFO));
                    pCryptAttr = SignerInfo.AuthAttrs.rgAttr;
                    for (int j = 0; j < SignerInfo.AuthAttrs.cAttr; j++)
                    {
                        CryptAttr = (CRYPT_ATTRIBUTE)Marshal.PtrToStructure(pCryptAttr, typeof(CRYPT_ATTRIBUTE));
                        if (CryptAttr.pszObjId.Equals(szOID_RSA_signingTime))
                        {
                            CryptAttrBlob = (BLOB)Marshal.PtrToStructure(CryptAttr.rgValue, typeof(BLOB));

                            cbFileTime = Marshal.SizeOf(lFileTime);
                            bResult = CryptDecodeObject(
                                X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
                                szOID_RSA_signingTime,
                                CryptAttrBlob.pbData,
                                CryptAttrBlob.cbData,
                                0,
                                out lFileTime,
                                ref cbFileTime
                            );
                            if (!bResult)
                            {
                                throw new Exception("CryptDecodeObject error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
                            }

                            // Found it. Add it to array
                            signingTimes[i] = DateTime.FromFileTime(lFileTime);

                            break;
                        }
                        pCryptAttr = new IntPtr(pCryptAttr.ToInt32() + Marshal.SizeOf(CryptAttr));
                    }

                    // Clean up
                    pSignerInfo.Dispose();
                }
            }
            finally
            {
                pSignerInfo.Dispose();
            }

            return signingTimes;
        }

        public static X509Certificate2Collection GetCerts(SafeMsgHandle hMsg)
        {
            // Variables
            X509Certificate2Collection certs = null;
            X509Certificate2 cert = null;
            SafeNTHeapHandle pbCert = SafeNTHeapHandle.Null;
            SafeCertContextHandle pCertContext = SafeCertContextHandle.Null;
            int iCount = 0;
            int cbCert = 0;

            try
            {
                // Get number of certificates
                GetMessageParam(hMsg, CMSG_CERT_COUNT_PARAM, out iCount);

                // Get certificates
                certs = new X509Certificate2Collection();
                for (int i = 0; i < iCount; i++)
                {
                    // Get binary data for cert
                    pbCert = SafeNTHeapHandle.Null;
                    GetMessageParam(hMsg, CMSG_CERT_PARAM, i, out pbCert, out cbCert);

                    // Create a cert context with that data
                    pCertContext = SafeCertContextHandle.Null;
                    pCertContext = CertCreateCertificateContext(
                        X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 
                        pbCert,
                        cbCert
                    );
                    if ((pCertContext == null) || (pCertContext.IsInvalid))
                    {
                        throw new Exception("CertCreateCertificateContext error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
                    }

                    // Create cert and add to collection
                    cert = new X509Certificate2(pCertContext.DangerousGetHandle());
                    certs.Add(cert);

                    // Clean up
                    pCertContext.Dispose();
                    pbCert.Dispose();
                }
            }
            finally
            {
                // Clean up
                pCertContext.Dispose();
                pbCert.Dispose();
            }

            return certs;
        }

        public static X509Certificate2Collection GetSignerCerts(SafeMsgHandle hMsg)
        {
            // Variables
            CMSG_SIGNER_INFO SignerInfo = new CMSG_SIGNER_INFO();
            X500DistinguishedName issuer = null;
            X509Certificate2Collection allCerts = null;
            X509Certificate2Collection certsByIssuer = null;
            X509Certificate2Collection certsBySerial = null;
            X509Certificate2Collection signerCerts = null;
            X509Certificate2 cert = null;
            SafeNTHeapHandle pSignerInfo = SafeNTHeapHandle.Null;
            string strSerial = null;
            byte[] pbIssuer = null;
            byte[] pbSerial = null;
            int iCount = 0;

            try
            {
                // Get number of signers
                iCount = GetSignerCount(hMsg);

                // Create cert collection 
                signerCerts = new X509Certificate2Collection();

                // Get all signers
                for (int i = 0; i < iCount; i++)
                {
                    // Get signer info
                    GetMessageParam(hMsg, CMSG_SIGNER_INFO_PARAM, i, out pSignerInfo);
                    SignerInfo = (CMSG_SIGNER_INFO)Marshal.PtrToStructure(pSignerInfo.DangerousGetHandle(), typeof(CMSG_SIGNER_INFO));

                    // Get signer's Issuer
                    pbIssuer = new byte[SignerInfo.Issuer.cbData];
                    Marshal.Copy(SignerInfo.Issuer.pbData, pbIssuer, 0, SignerInfo.Issuer.cbData);
                    issuer = new X500DistinguishedName(pbIssuer);

                    // Get signer's Serial Number in Hexadecimal
                    pbSerial = new byte[SignerInfo.SerialNumber.cbData];
                    Marshal.Copy(SignerInfo.SerialNumber.pbData, pbSerial, 0, SignerInfo.SerialNumber.cbData);
                    Array.Reverse(pbSerial);
                    strSerial = BitConverter.ToString(pbSerial);
                    strSerial = strSerial.Replace("-", "");

                    // Get all certs
                    allCerts = GetCerts(hMsg);

                    // Get certs with signer's Issuer
                    certsByIssuer = allCerts.Find(X509FindType.FindByIssuerDistinguishedName, issuer.Name, false);

                    // Get certs with signer's Serial Number
                    certsBySerial = certsByIssuer.Find(X509FindType.FindBySerialNumber, strSerial, false);

                    // There should be only one. Get it
                    cert = certsBySerial[0];

                    // Add the signer cert we found to the collection
                    signerCerts.Add(cert);

                    // Clean up 
                    pSignerInfo.Dispose();
                }
            }
            finally
            {
                // Clean up
                pSignerInfo.Dispose();
            }

            // Return the cert collection
            return signerCerts;
        }

        public static X509Certificate2Collection GetRecipientCerts(SafeMsgHandle hMsg)
        {
            // Variables
            CERT_INFO CertInfo = new CERT_INFO();
            X500DistinguishedName issuer = null;
            X509Certificate2Collection allCerts = null;
            X509Certificate2Collection certsByIssuer = null;
            X509Certificate2Collection certsBySerial = null;
            X509Certificate2Collection recipients = null;
            X509Certificate2 cert = null;
            SafeNTHeapHandle pCertInfo = SafeNTHeapHandle.Null;
            string strSerial = null;
            byte[] pbIssuer = null;
            byte[] pbSerial = null;
            int iCount = 0;

            try
            {
                // Get number of recipients
                iCount = GetRecipientCount(hMsg);

                // Create cert collection 
                recipients = new X509Certificate2Collection();

                // Get all recipients
                for (int i = 0; i < iCount; i++)
                {
                    // Get recipient info
                    GetMessageParam(hMsg, CMSG_RECIPIENT_INFO_PARAM, i, out pCertInfo);
                    CertInfo = (CERT_INFO)Marshal.PtrToStructure(pCertInfo.DangerousGetHandle(), typeof(CERT_INFO));

                    // Get recipient's Issuer
                    pbIssuer = new byte[CertInfo.Issuer.cbData];
                    Marshal.Copy(CertInfo.Issuer.pbData, pbIssuer, 0, CertInfo.Issuer.cbData);
                    issuer = new X500DistinguishedName(pbIssuer);

                    // Get recipient's Serial Number in Hexadecimal
                    pbSerial = new byte[CertInfo.SerialNumber.cbData];
                    Marshal.Copy(CertInfo.SerialNumber.pbData, pbSerial, 0, CertInfo.SerialNumber.cbData);
                    Array.Reverse(pbSerial);
                    strSerial = BitConverter.ToString(pbSerial);
                    strSerial = strSerial.Replace("-", "");

                    // Get all certs
                    allCerts = GetCerts(hMsg);

                    // Get certs with recipient's Issuer
                    certsByIssuer = allCerts.Find(X509FindType.FindByIssuerDistinguishedName, issuer.Name, false);

                    // Get certs with recipient's Serial Number
                    certsBySerial = certsByIssuer.Find(X509FindType.FindBySerialNumber, strSerial, false);

                    // There should be only one. Get it
                    cert = certsBySerial[0];

                    // Add the recipient cert we found to the collection
                    recipients.Add(cert);

                    // Clean up 
                    pCertInfo.Dispose();
                }
            }
            finally
            {
                // Clean up
                pCertInfo.Dispose();
            }

            // Return the cert collection
            return recipients;
        }

        public static void CheckEnvelopeAlg(SafeMsgHandle hMsg)
        {
            // Variables
            CRYPT_ALGORITHM_IDENTIFIER AlgId = new CRYPT_ALGORITHM_IDENTIFIER();
            SafeNTHeapHandle pEnvelopeAlg = SafeNTHeapHandle.Null;

            try
            {
                // Get envelope encryption algorithm
                GetMessageParam(hMsg, CMSG_ENVELOPE_ALGORITHM_PARAM, out pEnvelopeAlg);
                AlgId = (CRYPT_ALGORITHM_IDENTIFIER)Marshal.PtrToStructure(pEnvelopeAlg.DangerousGetHandle(), typeof(CRYPT_ALGORITHM_IDENTIFIER));
            }
            finally
            {
                // Clean up
                pEnvelopeAlg.Dispose();
            }
        }

        public static int GetSignerCount(SafeMsgHandle hMsg)
        {
            // Variables
            int iCount = 0;

            // Get signer count
            GetMessageParam(hMsg, CMSG_SIGNER_COUNT_PARAM, out iCount);

            // Return the count
            return iCount;
        }

        public static int GetRecipientCount(SafeMsgHandle hMsg)
        {
            // Variables
            int iCount = 0;

            // Get signer count
            GetMessageParam(hMsg, CMSG_RECIPIENT_COUNT_PARAM, out iCount);

            // Return the count
            return iCount;
        }

        public static void AddSigner(SafeMsgHandle hMsg, CMSG_SIGNER_ENCODE_INFO signerEncodeInfo)
        {
            // Variables
            bool bResult = false;

            // Add signer to message
            bResult = CryptMsgControl(
                hMsg,
                0,
                CMSG_CTRL_ADD_SIGNER,
                ref signerEncodeInfo
            );
            if (!bResult)
            {
                throw new Exception("CryptMsgControl error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
            }
        }

        public static void AddCerts(SafeMsgHandle hMsg, X509Certificate2Collection newCerts)
        {
            // Variables
            CERT_CONTEXT CertContext = new CERT_CONTEXT();
            BLOB CertBlob = new BLOB();
            X509Certificate2Collection previousCerts = null;
            bool bResult = false;

            // Get all certs already in the message
            previousCerts = GetCerts(hMsg);

            // Check if new certs are already in the message
            foreach (X509Certificate2 cert in newCerts)
            {
                if (previousCerts.Find(X509FindType.FindByThumbprint, cert.Thumbprint, false).Count == 0)
                {
                    // Get context of the cert
                    CertContext = (CERT_CONTEXT)Marshal.PtrToStructure(cert.Handle, typeof(CERT_CONTEXT));

                    // Get cert blob
                    CertBlob.cbData = CertContext.cbCertEncoded;
                    CertBlob.pbData = CertContext.pbCertEncoded;

                    // Add cert to message
                    bResult = CryptMsgControl(
                        hMsg,
                        0,
                        CMSG_CTRL_ADD_CERT,
                        ref CertBlob
                    );
                    if (!bResult)
                    {
                        throw new Exception("CryptMsgControl error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
                    }
                }
            }
        }

        public static void UpdateMessage(SafeMsgHandle hMsg, FileStream reencodedFile)
        {
            // Variables
            SafeNTHeapHandle pMessage = SafeNTHeapHandle.Null;
            byte[] pbMessage = null;
            int cbMessage = 0;

            try
            {
                // Update encoded message
                GetMessageParam(hMsg, CMSG_ENCODED_MESSAGE, 0, out pMessage, out cbMessage);

                // Get message
                pbMessage = new byte[cbMessage];
                Marshal.Copy(pMessage.DangerousGetHandle(), pbMessage, 0, cbMessage); 

                // Save it to file
                reencodedFile.Write(pbMessage, 0, pbMessage.Length);
            }
            finally
            {
                pMessage.Dispose();
            }
        }

        public static void CheckSignature(SafeMsgHandle hMsg, int dwIndex)
        {
            // Variables
            CMSG_CTRL_VERIFY_SIGNATURE_EX_PARA VerifyParam = new CMSG_CTRL_VERIFY_SIGNATURE_EX_PARA();
            SafeNTHeapHandle pSignerCertInfo = SafeNTHeapHandle.Null;
            SafeStoreHandle hStore = SafeStoreHandle.Null;
            SafeCertContextHandle pSignerCertContext = SafeCertContextHandle.Null;
            bool bResult = false;

            try
            {
                // Get signer cert info
                GetMessageParam(hMsg, CMSG_SIGNER_CERT_INFO_PARAM, dwIndex, out pSignerCertInfo);

                // Open a cert store in memory with the certs from the message
                hStore = CertOpenStore(
                    CERT_STORE_PROV_MSG,
                    X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
                    IntPtr.Zero,
                    0,
                    hMsg
                );
                if ((hStore == null) || (hStore.IsInvalid))
                {
                    throw new Exception("CertOpenStore error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
                }

                // Find the signer's cert context in the store
                pSignerCertContext = CertGetSubjectCertificateFromStore(
                    hStore,
                    X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
                    pSignerCertInfo
                );
                if ((pSignerCertContext == null) || (pSignerCertContext.IsInvalid))
                {
                    throw new Exception("CertGetSubjectCertificateFromStore error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
                }

                VerifyParam.cbSize = Marshal.SizeOf(VerifyParam);
                VerifyParam.hCryptProv = IntPtr.Zero;
                VerifyParam.dwSignerIndex = dwIndex;
                VerifyParam.dwSignerType = CMSG_VERIFY_SIGNER_CERT;
                VerifyParam.pvSigner = pSignerCertContext.DangerousGetHandle();

                // Verify signature in message
                bResult = CryptMsgControl(
                    hMsg,
                    0,
                    CMSG_CTRL_VERIFY_SIGNATURE_EX,
                    ref VerifyParam
                );
                if (!bResult)
                {
                    throw new Exception("CryptMsgControl error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
                }
            }
            finally
            {
                // Clean up
                pSignerCertContext.Dispose();
                hStore.Dispose();
                pSignerCertInfo.Dispose();
            }
        }

        public static void CheckSignatures(SafeMsgHandle hMsg)
        {
            // Variables
            int iCount = 0;

            // Get number of signers
            iCount = GetSignerCount(hMsg);

            // Verify all signatures
            for (int i = 0; i < iCount; i++)
            {
                // Verify the signature of one signer
                CheckSignature(hMsg, i);
            }
        }

        public static void Decrypt(SafeMsgHandle hMsg)
        {
            // Variables
            CRYPT_KEY_PROV_INFO KeyProvInfo = new CRYPT_KEY_PROV_INFO();
            CMSG_CTRL_DECRYPT_PARA DecryptPara = new CMSG_CTRL_DECRYPT_PARA();
            SafeNTHeapHandle pCertInfo = SafeNTHeapHandle.Null;
            SafeStoreHandle hStore = SafeStoreHandle.Null;
            SafeCertContextHandle pCertContext = SafeCertContextHandle.Null;
            SafeNTHeapHandle pKeyProvInfo = SafeNTHeapHandle.Null;
            SafeCSPHandle hProv = SafeCSPHandle.Null;
            bool bResult = false;

            try
            {
                // Get recipient cert
                GetMessageParam(hMsg, CMSG_RECIPIENT_INFO_PARAM, out pCertInfo);

                // Open personal cert store
                hStore = CertOpenSystemStore(
                    IntPtr.Zero,
                    "MY"
                );
                if ((hStore == null) || (hStore.IsInvalid))
                {
                    throw new Exception("CertOpenStore error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
                }

                // Find recipient cert in store
                pCertContext = CertGetSubjectCertificateFromStore(
                    hStore,
                    X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
                    pCertInfo
                );
                if ((pCertContext == null) || (pCertContext.IsInvalid))
                {
                    throw new Exception("CertGetSubjectCertificateFromStore error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
                }

                // Get key info for that cert
                GetCertContextProperty(pCertContext, CERT_KEY_PROV_INFO_PROP_ID, out pKeyProvInfo);
                KeyProvInfo = (CRYPT_KEY_PROV_INFO)Marshal.PtrToStructure(pKeyProvInfo.DangerousGetHandle(), typeof(CRYPT_KEY_PROV_INFO));

                // Get CSP for that key
                bResult = CryptAcquireContext(
                    out hProv,
                    KeyProvInfo.pwszContainerName,
                    KeyProvInfo.pwszProvName,
                    KeyProvInfo.dwProvType,
                    0
                );
                if (!bResult)
                {
                    throw new Exception("CryptAcquireContext error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
                }

                // Prepare decryption
                DecryptPara.cbSize = Marshal.SizeOf(DecryptPara);
                DecryptPara.hCryptProvOrNCryptKey = hProv.DangerousGetHandle();
                DecryptPara.dwKeySpec = KeyProvInfo.dwKeySpec;
                DecryptPara.dwRecipientIndex = 0;

                // Decrypt the message
                bResult = CryptMsgControl(
                    hMsg, 
                    0, 
                    CMSG_CTRL_DECRYPT,
                    ref DecryptPara
                );
                if (!bResult)
                {
                    throw new Exception("CryptMsgControl error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
                }
            }
            finally 
            {
                // Clean up
                pCertContext.Dispose();
                hStore.Dispose();
                pCertInfo.Dispose();
                //hProv.Dispose(); // This is not needed. We get an AV when disposing the message handle if we do this now.
                pKeyProvInfo.Dispose();
            }
        }

        #endregion
    }
}
